/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2024 Houmo AI Corporation
 * All rights reserved.
 */

#ifndef __SHM_FIFO_H__
#define __SHM_FIFO_H__

#ifndef __packed
#define __packed					__attribute__((__packed__))
#endif

#ifndef static_assert
#define static_assert(e)
#endif

/* max(riscv, arm, host) */
#define SHM_CACHELINE_BYTES				64

#ifndef shm_flush_cache
#define shm_flush_cache(addr, size)			do {} while (0)
#endif
#ifndef shm_invalidate_cache
#define shm_invalidate_cache(addr, size)		do {} while (0)
#endif

#ifndef offsetof
#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)
#endif

#ifndef READ_ONCE
#define READ_ONCE(x)					(x)
#endif

#ifndef WRITE_ONCE
#define WRITE_ONCE(x, val)				({ (x) = (val); })
#endif

/**
 * struct shm_fifo - like struct __kfifo
 * @mask:		mask = (size - 1), size = 2^n, the unit is bytes
 * @esize:		size of element
 * @data_offset:data_offset = data_addr - shm_fifo base addr
 * @out:		offset by bytes
 * @pad:		force out and in two cachelines
 * @in:			offset by bytes
 */
struct shm_fifo {
	uint32_t mask;
	uint32_t esize;
	int32_t data_offset;
	uint32_t in;
	struct {
		char __pad[0];
	} pad __aligned(SHM_CACHELINE_BYTES);
	uint32_t out;
} __aligned(SHM_CACHELINE_BYTES) __packed;

/* static check at compile time */
static_assert(offsetof(struct shm_fifo, in) / 64 != offsetof(struct shm_fifo, out) / 64);

static inline int shm_fifo_initialized(struct shm_fifo *fifo)
{
	return fifo->mask && ((fifo->mask + 1) & fifo->mask) == 0;
}

static inline int shm_fifo_init(struct shm_fifo *fifo, void *data, uint32_t size,
				uint32_t esize)
{
	int32_t offset = (long)(void *)data - (long)(void *)fifo;

	if (size == 0)
		return -EINVAL;

	if (size % esize)
		return -EINVAL;

	/* size is not 2^n ? */
	if (size & (size - 1))
		return -EINVAL;

	if (READ_ONCE(fifo->mask) == size - 1 && READ_ONCE(fifo->data_offset) == offset)
		return -EBUSY;

	WRITE_ONCE(fifo->in, 0);
	WRITE_ONCE(fifo->out, 0);
	WRITE_ONCE(fifo->mask, size - 1);
	WRITE_ONCE(fifo->esize, esize);
	WRITE_ONCE(fifo->data_offset, offset);

	shm_flush_cache(fifo, sizeof(*fifo));

	return 0;
}

static inline uint32_t shm_fifo_size(struct shm_fifo *fifo)
{
	return fifo->mask + 1;
}

static inline uint32_t shm_fifo_used_len(struct shm_fifo *fifo)
{
	shm_invalidate_cache(&fifo->in, sizeof(fifo->in));
	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));

	return READ_ONCE(fifo->in) - READ_ONCE(fifo->out);
}

static inline uint32_t shm_fifo_unused_len(struct shm_fifo *fifo)
{
	shm_invalidate_cache(&fifo->in, sizeof(fifo->in));
	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));

	return (fifo->mask + 1) - (READ_ONCE(fifo->in) - READ_ONCE(fifo->out));
}

static inline int shm_fifo_is_empty(struct shm_fifo *fifo)
{
	shm_invalidate_cache(&fifo->in, sizeof(fifo->in));
	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));

	return READ_ONCE(fifo->in) == READ_ONCE(fifo->out);
}

static inline int shm_fifo_is_full(struct shm_fifo *fifo)
{
	return shm_fifo_used_len(fifo) > fifo->mask;
}

static inline void *shm_fifo_data(struct shm_fifo *fifo, uint32_t offset)
{
	return (void *)fifo + fifo->data_offset + (offset & fifo->mask);
}

static inline void *shm_fifo_data_mem(struct shm_fifo *fifo)
{
	if (shm_fifo_is_empty(fifo))
		return NULL;

	return shm_fifo_data(fifo, READ_ONCE(fifo->out));
}

static inline int shm_fifo_enqueue_len(struct shm_fifo *fifo, void *data, uint32_t len)
{
	void *pos;
	uint32_t in;

	if (shm_fifo_unused_len(fifo) < len)
		return -EBUSY;

	shm_invalidate_cache(&fifo->in, sizeof(fifo->in));
	in = READ_ONCE(fifo->in);
	pos = shm_fifo_data(fifo, in);
	memcpy(pos, data, len);
	WRITE_ONCE(fifo->in, in + len);
	shm_flush_cache(pos, len);
	shm_flush_cache(&fifo->in, sizeof(fifo->in));

	return 0;
}

static inline int shm_fifo_skip_len(struct shm_fifo *fifo, uint32_t len);

static inline int shm_fifo_enqueue_overlay_len(struct shm_fifo *fifo, void *data, uint32_t len)
{
	void *pos;
	uint32_t in;

	if (shm_fifo_unused_len(fifo) < len)
		shm_fifo_skip_len(fifo, len);

	shm_invalidate_cache(&fifo->in, sizeof(fifo->in));
	in = READ_ONCE(fifo->in);
	pos = shm_fifo_data(fifo, in);
	memcpy(pos, data, len);
	WRITE_ONCE(fifo->in, in + len);
	shm_flush_cache(pos, len);
	shm_flush_cache(&fifo->in, sizeof(fifo->in));

	return 0;
}

static inline int shm_fifo_dequeue_len(struct shm_fifo *fifo, void *dest, uint32_t len)
{
	void *pos;
	uint32_t out;

	if (shm_fifo_used_len(fifo) < len)
		return -ENXIO;

	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));
	out = READ_ONCE(fifo->out);
	pos = shm_fifo_data(fifo, out);
	shm_invalidate_cache(pos, len);
	memcpy(dest, pos, len);
	WRITE_ONCE(fifo->out, out + len);
	shm_flush_cache(&fifo->out, sizeof(fifo->out));

	return 0;
}

/**
 * shm_fifo_peek_len - get data from the fifo without removing
 */
static inline int shm_fifo_peek_len(struct shm_fifo *fifo, void *dest, uint32_t len)
{
	void *pos;

	if (shm_fifo_used_len(fifo) < len)
		return -ENXIO;

	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));
	pos = shm_fifo_data(fifo, READ_ONCE(fifo->out));
	shm_invalidate_cache(pos, len);
	memcpy(dest, pos, len);

	return 0;
}

/**
 * shm_fifo_skip_len - skip/remove output data
 */
static inline int shm_fifo_skip_len(struct shm_fifo *fifo, uint32_t len)
{
	if (shm_fifo_used_len(fifo) < len)
		return -ENXIO;

	shm_invalidate_cache(&fifo->out, sizeof(fifo->out));
	WRITE_ONCE(fifo->out, READ_ONCE(fifo->out) + len);
	shm_flush_cache(&fifo->out, sizeof(fifo->out));

	return 0;
}

static inline uint32_t shm_fifo_used_count(struct shm_fifo *fifo)
{
	return shm_fifo_used_len(fifo) / fifo->esize;
}

static inline uint32_t shm_fifo_unused_count(struct shm_fifo *fifo)
{
	return shm_fifo_unused_len(fifo) / fifo->esize;
}

static inline int shm_fifo_enqueue(struct shm_fifo *fifo, void *data)
{
	return shm_fifo_enqueue_len(fifo, data, fifo->esize);
}

static inline int shm_fifo_enqueue_overlay(struct shm_fifo *fifo, void *data)
{
	return shm_fifo_enqueue_overlay_len(fifo, data, fifo->esize);
}

static inline int shm_fifo_dequeue(struct shm_fifo *fifo, void *dest)
{
	return shm_fifo_dequeue_len(fifo, dest, fifo->esize);
}

/**
 * shm_fifo_peek - get data from the fifo without removing
 */
static inline int shm_fifo_peek(struct shm_fifo *fifo, void *dest)
{
	return shm_fifo_peek_len(fifo, dest, fifo->esize);
}

/**
 * shm_fifo_skip - skip/remove output data
 */
static inline int shm_fifo_skip(struct shm_fifo *fifo)
{
	return shm_fifo_skip_len(fifo, fifo->esize);
}

#endif

